﻿/**
 * Naver Dev Center : Open API
 * http://dev.naver.com/openapi/
 * Remark: This library is individualy developed and NOT a official library from Naver/NHN.
 */
using System;
using System.Net;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Json;
using System.Xml.Serialization;
using NaverOpenApiLibrary.Exceptions;

namespace NaverOpenApiLibrary.Services
{
    /// <summary>
    /// Abstract class that handles overall Naver Open APIs
    /// </summary>
    /// <typeparam name="T">Return type after calling API service</typeparam>
    public abstract class NaverApiService<T>
    {
        protected string API_BASE_URl;
        protected string API_KEY;
        protected IDictionary<string, string> _uriParams;

        public NaverApiService(string apiKey, string apiUri)
        {
            API_KEY = apiKey;
            API_BASE_URl = apiUri;
        }

        protected T _serviceResult;

        // Implemented with "Template Method Pattern"
        /// <summary>
        /// Processing API task. GetApiUriParameters() and Deserialize(string) should be implemented before calling this. 
        /// Will do HTTP GET action with composed url and ApiServiceCompleted event will be raised after retrieving data
        /// </summary>
        protected void DoApiService()
        {
            WebClient client = new WebClient();
            client.DownloadStringCompleted += (s, e) =>
            {
                if (e.Error == null)
                    _serviceResult = Deserialize(e.Result);
                else
                    _serviceResult = default(T);

                OnApiServiceCompleted();
            };
            client.DownloadStringAsync(new Uri(AssembleApiUri(API_BASE_URl, API_KEY)), UriKind.Absolute);
        }

        // virtual methods
        protected virtual string AssembleApiUri(string apiBaseUrl, string apiKey)
        {
            this.API_KEY = apiKey;
            this.API_BASE_URl = apiBaseUrl;
            return AssembleApiUri();
        }

        protected virtual string AssembleApiUri(string apiBaseUrl)
        {
            this.API_BASE_URl = apiBaseUrl;
            return AssembleApiUri();
        }

        /// <summary>
        /// Assembles URI to reqest with API key and other parameters that is assembled from GetApiUriParameters()
        /// </summary>
        /// <returns>Assembled URI to request</returns>
        protected virtual string AssembleApiUri()
        {
            this._uriParams = GetApiUriParameters();

            if (this.API_KEY == null || this.API_BASE_URl == null) throw new ApiException("API key or base url is not initialized");

            StringBuilder uriBuilder = new StringBuilder(this.API_BASE_URl);
            if (uriBuilder[uriBuilder.Length - 1] != '?') uriBuilder.Append("?");

            if (this._uriParams != null)
            {
                // appends "uriParameter=value&"
                foreach (KeyValuePair<string, string> pair in this._uriParams)
                {
                    uriBuilder.Append(pair.Key).Append("=").Append(pair.Value).Append("&");
                }
            }
            if (this.API_KEY != null) uriBuilder.Append("key=").Append(this.API_KEY);

            return uriBuilder.ToString();
        }

        // abstract methods
        /// <summary>
        /// returns URI parameters to append (except api key)
        /// </summary>
        /// <returns>Dictionary list with [parameter,value] pairs</returns>
        protected abstract IDictionary<string, string> GetApiUriParameters();
        /// <summary>
        /// returns deserialized object from the parameter "rawData"
        /// usually implemented with base.ParseJsonResult(...); or base.ParseXmlResult(...);
        /// </summary>
        /// <param name="rawData">serialized data</param>
        /// <returns>desirealized object</returns>
        protected abstract T Deserialize(string rawData);


        // parse options
        protected T ParseJsonResult(string jsonData)
        {
            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonData)))
            {
                DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T));
                T deserializedResult = default(T);
                try { deserializedResult = (T)deserializer.ReadObject(ms); }
                catch (InvalidOperationException ioe) { System.Diagnostics.Debug.WriteLine("[ParseJsonResult] An error has been occured while Parsing JSON: " + ioe.Message); }
                return deserializedResult;
            }
        }

        protected T ParseXmlResult(string xmlData)
        {
            using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(xmlData)))
            {
                XmlSerializer deserializer = new XmlSerializer(typeof(T));
                T deserializedResult = default(T);
                try { deserializedResult = (T)deserializer.Deserialize(ms); }
                catch (InvalidOperationException ioe) { System.Diagnostics.Debug.WriteLine("[ParseXmlResult] An error has been occured while Parsing XML: " + ioe.Message); }
                return deserializedResult;
            }
        }

        public delegate void ApiServiceStatus(object sender, T serviceResult);
        public event ApiServiceStatus ApiServiceCompleted;
        protected void OnApiServiceCompleted()
        {
            ApiServiceCompleted(this, _serviceResult);
        }
    }
}
